﻿/************************************************************************************
 * Xml file interface                                                               *
 * Crated by: Adam Reaves, Mike Flood (Lots of help from Edgar Torres)              *
 * Summary: Contains all the necessary functions to interact with the XML file      *
 *  Version 1 - 11/02/11                                                            *
 *  We do appologize if it isn't perfect, this is code written at 3:00am            *
*************************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

using System.Xml.Linq;
using System.Xml.Schema;
using System.Security.Cryptography;
using System.Threading;



namespace BadassPhotoAlbum
{
    class Database
    {
        //secret location of XML and photos along with some fixed name tags to interact with XML
        private string SECRETLOCATION = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\BadassPhoto\\";
        private string DATABASENAME = "BadassXML.xml";
        private string ROOTTAG = "AlbumList";

        //Because the databse is a "resource" it needs to be protected from simultanious access
        private Mutex dbMux;

        //actual XML file to interact with
        private XDocument xmlFile;
       
        //These are the next avaiable IDs that are assigned to photos whenever a new one is created
        private int nextAvailablePhotoID = 0;
        private int nextAvailableAlbumID = 0;

        //a unique photo list to speed up hash lookups and database lookups
        private List<Photo> uniquePhotos;

        public Database()
            /* **************************************************************************************************
             * Database Constructor                                                                             *
             * Adam Reaves, Mike Flood.                                                                         *
             * Initializes all the necessary components of the database.                                        *
             * On first start it creates the database                                                           *
             * If database already exists, it captures all the information from it and stores in quick lookup   *
             ****************************************************************************************************/
        {
            //check to see if secret folder exists
            try
            { //if it does, just open it
                xmlFile = XDocument.Load(SECRETLOCATION + DATABASENAME);
            }
            catch
            { //if it doesn't create it and open it
                xmlFile = CreateXMLFile();
            }
            //init the mutex
            dbMux = new Mutex();

            dbMux.WaitOne();
            //get the next available IDs from that database
            InitilizeIDs();
            //init the quick lookup table
            uniquePhotos = new List<Photo>();
            //populate the quick lookup table
            GetAllUniquePhotos();
            dbMux.ReleaseMutex();
        }

        public static int GenerateHash(Stream f)
        /* *************************************************************************************************
        * Static Member function to generate MD5 hashes                                                    *
        * Adam Reaves, Mike Flood.                                                                         *
        * Simple function that takes a file stream and generates a hash int                                *
        ****************************************************************************************************/
        {
            MD5 hasher = MD5.Create(); //create the actual hashing object
            Byte[] data = hasher.ComputeHash(f); //hash it
            return BitConverter.ToInt32(data, 0); //convert the byte array into a value
        }

        public string GetSecretLocal()
        /* *************************************************************************************************
        * Function that returns the secret location to outside objects                                     *
        * Adam Reaves, Mike Flood.                                                                         *
        * Simple function that returns the string location of the secret location                          *
        ****************************************************************************************************/
        {
            return SECRETLOCATION;
        }

        public List<Album> GetAllAlbums()
        /* *************************************************************************************************
        * A method that returns a large list of all the albums in the databse (does not populate them)     *
        * Adam Reaves, Mike Flood.                                                                         *
        * Should only be called ONCE!                                                                      *
        ****************************************************************************************************/
        {
            dbMux.WaitOne(); //aquire mutex

            List<Album> thelist = new List<Album>();
            for (int i = 0; i <= nextAvailableAlbumID; i++) //for every assigned ablum ID
            {
                //inquiry the databse where the attribute equals the request ID
                IEnumerable<XElement> listElem =
                    from elem in xmlFile.Elements(ROOTTAG)
                    from em in elem.Elements("Album")
                    where em.Attribute("Albumid").Value == i.ToString()
                    select em;

                XElement e = listElem.FirstOrDefault(); //only one should ever be found (IDs are unique for albums)
                if (e == null) 
                    continue; //this ID doesn't exists in the databse so, it doesn't need to be added

                int id;
                string desc;
                string name;

                //get the information from the database attributes
                id = Int32.Parse(e.Attribute("Albumid").Value);
                desc = (string)e.Element("AlbumDescription").Value;
                name = (string)e.Element("AlbumName").Value;

                //generate a new album
                Album tempAlbum = new Album(name, desc, id);

                //add it to the list to returns
                thelist.Add(tempAlbum);
            }

            dbMux.ReleaseMutex(); //release the mutex
            return thelist;
        }

        public List<Photo> GetAllPhotosFromAlbum(int albumID)
        /* *************************************************************************************************
        * A method that returns a large list of all the photos in an album                                 *
        * in the databse (does not populate the ID just returns a list                                     *
        * Adam Reaves, Mike Flood.                                                                         *
        * Should only be called ONCE!                                                                      *
        ****************************************************************************************************/
        {

            dbMux.WaitOne(); //aquire mutex
            List<Photo> thelist = new List<Photo>();

            //inquiry the dabase for all albums with albumID
            IEnumerable<XElement> listElem =
                from elem in xmlFile.Elements(ROOTTAG)
                from em in elem.Elements("Album")
                where em.Attribute("Albumid").Value == albumID.ToString()
                select em;

            //should only be one (album ID are unique)
            XElement theAlbum = listElem.FirstOrDefault();

            if (theAlbum == null)
            {
                dbMux.ReleaseMutex();
                return null; //the ablum was deleted from the database already
            }

            listElem =
                from elem in theAlbum.Elements("Photo")
                select elem;

            foreach (XElement ph in listElem)
            {
                //all the photos exist in the unique photo list already, so just ID the photo and add it to the album
                thelist.Add(GetUniquePhotoById(Int32.Parse(ph.Attribute("Photoid").Value)));
            }
            dbMux.ReleaseMutex(); //release mutex
            return thelist; 
        }

        public bool AddAlbum(Album newalbum)
        /* *************************************************************************************************
        * A method that adds a new album to the databse                                                    *
        * Adam Reaves, Mike Flood.                                                                         *
        ****************************************************************************************************/
        {
            dbMux.WaitOne(); //aquire mutex

            //create a new element with all the properties of the album
            XElement E = new XElement("Album", new XAttribute("Albumid", newalbum.getAlbumID()),
                            new XElement("AlbumName", newalbum.AlbumName),
                            new XElement("AlbumDescription", newalbum.AlbumDescription));
            //add it... too simple!
            xmlFile.Root.Add(E);
            
            //increment the albumIDs
            //nextAvailableAlbumID++;
            //update this ID in the databse
            xmlFile.Root.Element("Info").Attribute("AlbumIndex").Value = nextAvailableAlbumID.ToString();

            xmlFile.Save(SECRETLOCATION + DATABASENAME);

            dbMux.ReleaseMutex(); //release mutex
            return true;
        }

        public int RequestNextAlbumID()
        /* *************************************************************************************************
        * gets the next available albumID                                                                  *
        * Adam Reaves, Mike Flood.                                                                         *
        ****************************************************************************************************/
        {
            //dbMux.WaitOne();//prevents an id from being obtained until ctor is complete
            //dbMux.ReleaseMutex();
            nextAvailableAlbumID++;
            return nextAvailableAlbumID;
        }

        public int RequestNextPhotoID()
        /* *************************************************************************************************
        * gets the next available PhotoID                                                                  *
        * Adam Reaves, Mike Flood.                                                                         *
        ****************************************************************************************************/
        {
            //dbMux.WaitOne(); //prevents an id from being obtained until ctor is complete
            //dbMux.ReleaseMutex();
            nextAvailablePhotoID++;
            return nextAvailablePhotoID;
        }

        public bool DeleteAlbum(int albumid)
        /* *************************************************************************************************
        * Deletes the ablum with albumID as it's attribute                                                 *
        * Adam Reaves, Mike Flood.                                                                         *
        * Returns true if it was sucessfully deleted                                                       *
        * false if not deleted for any reason                                                              *
        ****************************************************************************************************/
        {
            dbMux.WaitOne(); //aquire mutex

            //search for every album with album Id as it's attribute
            IEnumerable<XElement> remAlbum =
                from elem in xmlFile.Elements(ROOTTAG)
                from em in elem.Elements("Album")
                where em.Attribute("Albumid").Value == albumid.ToString()
                select em;

            if (remAlbum.FirstOrDefault() == null) //album ID doesn't exist to delete
            {
                dbMux.ReleaseMutex();
                return false; //indicate to caller
            }

            remAlbum.FirstOrDefault().Remove(); //remove it (IDs are unique)
            xmlFile.Save(SECRETLOCATION + DATABASENAME);

            dbMux.ReleaseMutex(); //release mutex
            return true;
        }
        public void MovePhotoInAlbumToIndex(int albumid, int photoid, int index)
        /* *************************************************************************************************
        * Takes an albun and a photo and moves it to index                                                 *
        * Adam Reaves                                                                                      *                                                           *
        ****************************************************************************************************/
        {
            dbMux.WaitOne();

            //find the album
            IEnumerable<XElement> E =
                from elem in xmlFile.Elements(ROOTTAG)
                from em in elem.Elements("Album")
                where em.Attribute("Albumid").Value == albumid.ToString()
                select em;

            if (E.FirstOrDefault() == null)
            {
                dbMux.ReleaseMutex();
                return; //album ID doesn't exist
            }

            //find photo with this photoID
            IEnumerable<XElement> remPhotolist =
                from elem in E.FirstOrDefault().Elements("Photo")
                where elem.Attribute("Photoid").Value == photoid.ToString()
                select elem;

            XElement modPhoto = remPhotolist.FirstOrDefault(); //get photo
            if (modPhoto == null)
            {
                dbMux.ReleaseMutex();
                return; //this photo ID doesn't exist
            }

            //remove this node
            modPhoto.Remove();

            //get all the other photos in this album
            IEnumerable<XElement> photos = E.FirstOrDefault().Elements("Photo");

            //check to see if this index will be valid
            if (index > photos.Count()) //TODO check to see if this should be 2
                index = photos.Count(); //make it valid if not
            else if (index < 0)
                index = 0;

            //add before index
            photos.ElementAt(index).AddAfterSelf(modPhoto);

            xmlFile.Save(SECRETLOCATION + DATABASENAME);

            dbMux.ReleaseMutex();

            return;
        }
        /* *************************************************************************************************
        * Adds a photo to the album then moves it's index in the XML file                                  *
        * Adam Reaves                                                                                      *
        * Returns true if it was sucessfully added                                                         *
        * false if not added                                                                               *
        ****************************************************************************************************/
        public bool AddPhotoToAlbum(int albumid, Photo newphoto, int index)
        {
            if (!AddPhotoToAlbum(albumid, newphoto, index)) //based on this, we know that albumID exists
                return false;

            MovePhotoInAlbumToIndex(albumid, newphoto.GetPhotoID(), index);

            return true;

        }
        public bool AddPhotoToAlbum(int albumid, Photo newphoto)
        /* *************************************************************************************************
        * Adds the precreated photo into albumID                                                           *
        * Adam Reaves, Mike Flood.                                                                         *
        * Returns true if it was sucessfully added                                                         *
        * false if not added for any reason                                                                *
        ****************************************************************************************************/
        {
            if (!CheckPhotoExistance(newphoto.GetHashValue()))  //first check to see if it already exists
            {                                                   //in the quick lookup table
                //nextAvailablePhotoID++; //if it doesn't make one
                xmlFile.Root.Element("Info").Attribute("PhotoIndex").Value = nextAvailablePhotoID.ToString();
            }

            dbMux.WaitOne(); //aquire mutex

            //look up every node that has the attribute albumID
            IEnumerable<XElement> E =
                from elem in xmlFile.Elements(ROOTTAG)
                from em in elem.Elements("Album")
                    where em.Attribute("Albumid").Value == albumid.ToString()
                    select em;

            if (E.FirstOrDefault() == null) //if this ID doesn't exist, indicate it to the caller
            {
                dbMux.ReleaseMutex();
                return false;
            }

            //add a new node under the found node with the new photo's attributes
            E.FirstOrDefault().Add(new XElement("Photo", new XAttribute("Photoid", newphoto.GetPhotoID()),
                                            new XAttribute("HashValue", newphoto.GetHashValue()),
                                            new XElement("PhotoName", newphoto.PhotoName),
                                            new XElement("PhotoDescription", newphoto.PhotoDescription)));

            xmlFile.Save(SECRETLOCATION + DATABASENAME);
            dbMux.ReleaseMutex(); //release mutex

            return true;
        }

        public bool DeletePhotoFromAlbum(int albumid, int photoid)
        /* *************************************************************************************************
        * deletes a photo from the ablum with albumID and photo photoID                                    *
        * Adam Reaves, Mike Flood.                                                                         *
        * Returns true if it was sucessfully deleted                                                       *
        * false if not deleted for any reason                                                              *
        ****************************************************************************************************/
        {
            dbMux.WaitOne(); //aquire mutex

            //find all nodes with albumID == albumId
            IEnumerable<XElement> remAlbum =
                from elem in xmlFile.Elements(ROOTTAG)
                from em in elem.Elements("Album")
                where em.Attribute("Albumid").Value == albumid.ToString()
                select em;

            if (remAlbum.FirstOrDefault() == null)
            {
                dbMux.ReleaseMutex();
                return false;
            }

            //find all photos in above inquiry with photoID
            IEnumerable<XElement> remPhoto =
                from elem in remAlbum.FirstOrDefault().Elements("Photo")
                where elem.Attribute("Photoid").Value == photoid.ToString()
                select elem;

            //remove this photo
            remPhoto.FirstOrDefault().Remove();
            xmlFile.Save(SECRETLOCATION + DATABASENAME);

            dbMux.ReleaseMutex(); //release mutex
            return true;

        }

        public bool CheckPhotoExistance(int hash)
        /* *************************************************************************************************
        * Reads database and checks to see if a photo with hashvalue hash exists                           *
        * Adam Reaves, Mike Flood.                                                                         *
        * Returns true if found                                                                            *
        * false if not found                                                                               *
        ****************************************************************************************************/
        {
            dbMux.WaitOne(); //aquire mutex
            IEnumerable<XElement> E = null;

            //searches for every instance of photo 
            E = from elem in xmlFile.Elements(ROOTTAG)
                from em in elem.Elements("Album")
                from en in em.Elements("Photo")
                select en;

            //checks to see if any photos were found
            if (E.FirstOrDefault() == null)
            {
                dbMux.ReleaseMutex(); //release mutex
                return false; //if not, it must not exist
            }

            foreach (XElement e in E) //check every element to see if it has the correct hash
            {
                if (e.Attribute("HashValue").Value == hash.ToString())
                {
                    dbMux.ReleaseMutex(); //release mutex
                    return true; //if so, be done!
                }
            }
            dbMux.ReleaseMutex(); //release mutex
            return false; //if not, it's not in the DB

        }

        private XDocument CreateXMLFile()
        /* *************************************************************************************************
        * Creates an XML file in secret location and populates it with the root info                       *
        * Adam Reaves, Mike Flood.                                                                         *
        * Returns  the created document                                                                     *
        ****************************************************************************************************/
        {
            //default values
            XDocument xdoc = new XDocument(new XDeclaration("1.0", "utf-8", "yes"), new XElement(ROOTTAG,null));
            XElement info = new XElement("Info",new XAttribute("PhotoIndex",0), new XAttribute("AlbumIndex",0));

            Directory.CreateDirectory(SECRETLOCATION); //creat the location

            xdoc.Root.Add(info); //add some info describing how many photos and albums exist
            xdoc.Save(SECRETLOCATION + DATABASENAME);

            return xdoc;
        }

        private void InitilizeIDs()
        /* *************************************************************************************************
        * Read the info portion of the XML file for two special attributes                                 *
        * Adam Reaves, Mike Flood.                                                                         *                                                      *
        ****************************************************************************************************/
        {
            nextAvailablePhotoID = Int32.Parse(xmlFile.Root.Element("Info").Attribute("PhotoIndex").Value);
            nextAvailableAlbumID = Int32.Parse(xmlFile.Root.Element("Info").Attribute("AlbumIndex").Value);
        }

        public List<Photo> getUniquePhotoList()
        /* *************************************************************************************************
        * Gets the pregenerated unique photo list from the constructor                                     *
        * Adam Reaves, Mike Flood.                                                                         *
        ****************************************************************************************************/
        {
            return uniquePhotos;
        }

        private void GetAllUniquePhotos()
        /* *************************************************************************************************
        * Searches DB for every unique photo and populated the unique photo list                           *
        * Adam Reaves, Mike Flood.                                                                         *
        ****************************************************************************************************/
        {
            bool inlist = false; //false until proven otherwise
            int checkID;

            dbMux.WaitOne(); //release mutex

            for (int i = 0; i < nextAvailablePhotoID +1; i++) //search for every potential existing photo
            {
                //get every photo in the database with i as an ID
                IEnumerable<XElement> E =
                    from elem in xmlFile.Element(ROOTTAG).Elements("Album")
                    from em in elem.Elements("Photo")
                        where em.Attribute("Photoid").Value == i.ToString()
                        select em;

                if (E.Count() == 0) //if there are none, this ID doesn't exist in the database
                    continue;

                inlist = false;
                //get the ID to check for in the unique photo list
                checkID = Int32.Parse(E.FirstOrDefault().Attribute("Photoid").Value);

                foreach (Photo uf in uniquePhotos) //check in the unique photo list for this ID
                {
                    if (uf.GetPhotoID() == checkID) //if it's there
                    {
                        inlist = true; //indicate it's found
                        break; //skip generating this new photo.
                    }
                }
                if(!inlist)
                {
                    //this is the first time this photo has been encountered
                    int id;
                    string desc;
                    string name;
                    string path;

                    //generate a new photo
                    id = checkID;
                    desc = E.FirstOrDefault().Element("PhotoDescription").Value;
                    name = E.FirstOrDefault().Element("PhotoName").Value;
                    path = SECRETLOCATION + checkID.ToString();

                    //add it to the list
                    uniquePhotos.Add(new Photo(name, id, desc, path));
                }
            }
            dbMux.ReleaseMutex(); //release mutex
        }

        private Photo GetUniquePhotoById(int id)
        /* *************************************************************************************************
        * Searches unique photo list for ID                          *
        * Adam Reaves, Mike Flood.                                                                         *
        * Returns photo if found                                                         *
        * null if not in list                                                                *
        ****************************************************************************************************/
        {
            foreach (Photo p in uniquePhotos) //linear search
            {
                if (p.GetPhotoID() == id)
                    return p;
            }
            return null;
        }

        public bool modifyPhoto(int photoid, Photo modPhoto)
        /* *************************************************************************************************
        * Reads database changes every instance of photoid to the info in modphoto                         *
        * Adam Reaves, Mike Flood.                                                                         *
        * Returns true if it was sucessfully modified                                                         *
        * false if not added for any reason                                                                *
        ****************************************************************************************************/
         {
            dbMux.WaitOne(); //aquire mutex

            //look for every instance of the photo that matches ID
            IEnumerable<XElement> modP =
                from elem in xmlFile.Elements(ROOTTAG)
                from em in elem.Elements("Album")
                from pm in em.Elements("Photo")
                where pm.Attribute("Photoid").Value == photoid.ToString()
                select pm;

            if (modP.FirstOrDefault() == null)
            {
                dbMux.ReleaseMutex();
                return false; //there was no photo by this ID
            }

            //for each found photo
            foreach (XElement mP in modP)
            {
                //rewrite the node with the new info
                mP.Element("PhotoName").Value = modPhoto.PhotoName;
                mP.Element("PhotoDescription").Value = modPhoto.PhotoDescription;
            }
            xmlFile.Save(SECRETLOCATION + DATABASENAME);

            dbMux.ReleaseMutex(); //release mutex
            return true;
        }

        public bool ModifyAlbum(int albumid, Album modAlbum)
        /* *************************************************************************************************
        * Reads database and modifies album with albumID to info in modalbum                               *
        * Adam Reaves, Mike Flood.                                                                         *
        * Returns true if it was sucessfully modifid                                                       *
        * false if it didn't exist or for any other reason                                                 *
        ****************************************************************************************************/
        {
            dbMux.WaitOne(); //release mutex

            //search for every instance of albumID in the database
            IEnumerable<XElement> modA =
                from elem in xmlFile.Elements(ROOTTAG)
                from em in elem.Elements("Album")
                where em.Attribute("Albumid").Value == albumid.ToString()
                select em;

            if (modA.FirstOrDefault() == null) //there are no albums with albumID as it's attribute
            {
                dbMux.ReleaseMutex();
                return false;
            }

            foreach (XElement mA in modA) //modify every album found
            {
                mA.Element("AlbumName").Value = modAlbum.AlbumName;
                mA.Element("AlbumDescription").Value = modAlbum.AlbumDescription;
            }
            xmlFile.Save(SECRETLOCATION + DATABASENAME);

            dbMux.ReleaseMutex(); //release mutex
            return true;
        }
        public void ClearAndReWriteAlbum(Album album) //should be called for complete reorders
        {
            IEnumerable<XElement> modA =
            from elem in xmlFile.Elements(ROOTTAG)
            from em in elem.Elements("Album")
            where em.Attribute("Albumid").Value == album.getAlbumID().ToString()
            select em;

            XElement clearAlbum = modA.FirstOrDefault();

            if (clearAlbum == null)
                return;

            IEnumerable<XElement> photos = modA.FirstOrDefault().Elements("Photo");

            photos.Remove();// remove them all

            foreach (Photo p in album.getPhotoList())
                this.AddPhotoToAlbum(album.getAlbumID(), p);
        }
    }
}
